;;------------------------------------------------------------------------------ ;;Filename jimtech.txt ;;System ;;Author JIM TAYLOR ;;Date Wed 3 Sep 1997 ;;Description Technical programming information for Flying Corps ;;------------------------------------------------------------------------------ This file describes programming information for the extra files provided with Flying corps gold. The following areas are covered: Keyboard input Analogue interface Config file Mission editor Please understand that these programming details are extremely brief. Quite a bit of additional research will be required by a potential programmer. I don't know if anybody will ever actually look at this! Keyboard input ============== During the 3d sections, the keyboard is mapped into a bit lookup table. There is a 1 bit per keyboard scancode lookup which describes wether the key is currently pressed. In addition, each key and shift combination can be associated with 2 bits in a larger table. See keytest.h. When a key is pressed, both bits are set if the key is not already pressed. When the key is released, only 1 bit is cleared. Within the program, these bits are polled, and then the second bit is cleared. In keymaps.h, the macro Keyname() defines equate names for each bit pair. The mapping of scancodes and shiftkeys to these equates is now driven by the binary file FLY\keyboard\keyb3d.bin. By modifying this file, the keyboard mapping of the program can be changed. This file is generated by compiling the table KeyMapping in keymaps.h. Note that while this system supports 8 shift states only 5 work in Windows95 (unshifted, shift, alt, ctrl, and 101key), the 3 RHS shift keys are only distinguishable to the DOS program. Analogue interface ================== Another method of getting input from the program is via the analogue interface. This is a set of DLLs which can be automatically loaded by the program at runtime via the savegame file. As well as generating analogue axis inputs which will be recognised by the game, these routines can also emulate keyboard events and drive output effects for force feedback joysticks, for instance. We wish to make this interface public so that new devices can be supported and further tuning of our interface can take place. Sources for the existing drivers has been included. During calibration, the input routine is called once per screen refresh, perhaps 5 to 10 times a second. Once the 3d is running it is called every 3 centiseconds. We found that the windows driver simply stopped the computer if called 33 times a second, so the driver applys a stepping down factor. However, our DOS direct access driver runs far faster even under windows. Data structure -------------- The data structures are in the header file analogue.h There are 32 instances of Axis, and 8 instances of Device. The enum AxisUses indexes the axis array. MAKEFIELD() produces a bitfield called AxisUsesField. The instance of this in each Device indicates which axes the device will drive. Each device must own at least one axis which is polled by the game. Axis[]: Note that this structure is 'future proofed' Flying Corps only uses 7 of the axes for input, and FCG uses 11: flight: elevator, aileron, rudder, throttle, cooliehat front: UI_X,UI_Y views: head hdg, pitch, roll; zoom Axes 28 to 31 are dummies for DLLs which don't actually generate axis values. Devices owning one of these axes are always polled, but the results are ignored. Instead, these axes are written to by the program so that a force feedback device can respond to the effects. The axis record contains a number of fields which are intended to hold the calibration information for that axis. It is up to the driver to make the position field contain a value between -32767, 0, and +32767. A disabled or ignored axis returns 32768. In addition, the Throttle is ignored for readings of +/-32767. The signed values centre, min, and max are particularly useful, and I also use scale1 and scale2 as multiplication factors. The capitalised access functions Min(), Max(), etc. access the same word fields as absolute values. When used to represent feedback, the access functions FB_Lx() are intended to represent linear effects, and the functions FB_Rx() represent rotational effects. When the value is actually changed by the game, position will be set non-zero. In addition, the Axis record contains indexes to the "rowan text system" for printing calibration messages, and the status field indicates any errors during calibration. The equates for these texts are in text.h. A number of spares have been prepared starting at JOYSTIC_SPARE_2. Alternate text can be poked into the existing binary text files. See the english\text directory, and look for "DeviceNameSpare2DeviceNameSpare2" etcetera. The first 8 entries from JOYSTICK_DEVICE_NAME to JOYSTIC_SPARE_6 are printed in the driver selection list, and map directly to the order of the files in the file libcode\dir.dir. All the spares after 6 are unassigned. Device[]: As well as the "activeaxes" field which lists which axes the device will update and the "rowan text system" device name, which is actually used to identify the curent driver on the calibration screen, a small ammount of workspace is provided in the LastButtons field. The STUBLOADABLE and devfile are used to manage the loading of the driver, so should not be altered at runtime. Code interface ============== Initial loading of the driver code involves making a C++ virtual function table to access the Device member functions. In MS-DOS this is done by the main program. It matches the routines listed in analpub.cpp and creates a new table. The exact order and size of this list in analpub is critical. In Windows, the DEVICEHOOK() routine in the DLL provides this facility. These two interfaces can be used simultaniously, and are: ANALJOY.BIN. In both cases, a compilation datestamp is checked to ensure compatibility between the main program and the DLL. This will have to be modified in the DLLs to match the date the main program was last compiled. Unlike the traditional Windows COM, the main program owns the Device instances, and is able to store useful data in them. In MS-DOS, the DLL has no access to any data or functions except those provided as parameters. Any further facilities required must be managed independently through the DPMI interrupt INT31. Any handles or pointers can be held in the LastButtons field. The 5K low memory data area workspace is passed to all the functions (except close()) for use as required. The contents are not guaranteed between calls, but the address is. In windows, the DLL has full access to all system functions and data areas as normal. The following member functions are required: Bool Initialise(UByte*const workspace,Axis axes[]); -allows the DLL to set itself up. Bool LoadGame(UByte*const workspace,Axis axes[]); -called after the savegame record has been loaded Bool CloseDown(UByte*const workspace); -called before the driver is removed from memory void PreCalib(UByte*const workspace,Axis axes[],axisconfigpages& pages); -called on beginning to calibrate the joystick. activeaxes has been filled in with required options. The routine can initialise any axes it will be calibrating. The routine fills in pages. This is a list of 4 axis indexes per page displayed during configuration. The normal configuration processes 4 axes at once on 1 page, but you can force the display to be repeated up to 5 times. If no axes are nabled on the first page then configuration is skipped. This can be done if you trust the standard windows joystick configuration, or if your device returns fixed values anyway. Bool CalibCenter(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes); Bool CalibExtreme(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes); -These two routines are called continuously during the two calibration phases. For each axis flagged in reqaxis, the corresponding axes[].position should be filled in with estimates of the joystick position in the range -32K to +32K. Simultaniously, the values read should be used to calibrate the joystick, and the status should be set to indicate the progress. When the phase is over they simply cease to be called, and each axis's status field is checked. There is no "phase over" routine. They can cause calibration to cease by setting their return code true, indicating that a button on the stick was pressed. Bool ReadPosition(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes,keytests* keyboard); -This is the main joystick workhorse. Position values should be provided for all the reqaxes, but others can be filled in as well. Then, when these are requested the routine would return quickly. The keyboard routine gives access to the keyboard class instance, and pointers to routines to set and clear keys by their rowan key indexes rather than the scancodes. Additionally, the following prototypes are provided as templates for writing your routines. You don't have to use them, but I'd advise it: Bool ReadHWPos(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes,ULong* newbuttons); -gets raw values into the position fields Bool ScaleHWPos(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes); -converts the raw values into +/-32K Bool ReadButtons(UByte*const workspace,Axis axes[],const AxisUsesField& reqaxes,ULong* newbuttons,keytests* keyboard); -processes the buttons into key presses Compiling --------- I compile and link these programs using Watcom C++ ver 10.5. You are welcome to use whatever you like, but you may have problems with the optimisation of the analpub.cpp module which is used to interface with MS-DOS. Compile the analpub fully optimised and it turns into a series of jmp statements. Compile the main code module with stack checking turned off. Link analpub, then the main module, then any libraries you need. In MS-DOS only code between "ROWANLIB" markers will be loaded in. These markers are emitted into the code segment. See stub.m for more detail. The binaries generated live in the LIBCODE directory. They are referenced by the DIR.DIR file in that directory. This is the C type "char [][16];" There is a lot more information in the various header files. Config File =========== The main config file is savegame\settings.cfg and savegame\winset.cfg for windows. This file contains 3 elements: 1) A zero terminated date string which is compared with the main executable's date 2) The structure SaveData (savegame.h) which controls a wide number of effects 3) The structure Analogue (analogue.h) which contains all 8 devices and all 32 axes. In particular, the devices[] contain a word devfile field which identifies the DLL file to load. If zero that driver is not loaded, but if set to a certain value, this indicates the location of the DLL. The upper byte identifies the directory by accessing the COMMON.DIR file. LIBCODE is directory 201. The lower byte indexes the DIR.DIR file inside the directory. The displayed joystick is Devices[0] In MS-DOS the mouse input is drive by Devices[1] In windows the mouse input is handled by winmain. By directly modifying the savegame file before running the program, additional drivers can be made to load. Mission Editor ============== The mission editor provided with FCG is by no means comprehensive. If you should decide that you have nothing better to do for a few months, then you could try to do better. The basic feature of the mission editor is that it generates a textfile which is then compiled by the utility MAKEBFW.EXE. However, it reads in the binary file generated by MAKEBF. There is only ever one user generated source file in the usersrc directory. While the mission editor is unable to allow certain advanced features to be directly edited, these features should be preserved as a "complex" expression. These expressions are described in bfnumber.h. Note that while bfnumber is an internal representation of an expression the structure does not exactly mirror the binary files generated by MAKEBFW. MAKEBFW.EXE actually reads bfields\initial.res and then attempts to compile bfields\battle.src which contains the source for all the battlefields for the original campaigns, but because they are unchanged it then generates just one user battlefield. The actual syntax is: MAKEBFW bfields\initial.res bfields\battle.src Optionally, "-M" stops uidvals.g from being generated (destroyed), OR "." generates the file textfour.err in directory ".". Textfour.err shows the internal tree structure built by MAKEBFW, and is over 14MB long. Generating it also quadrouples the duration of MAKEBFW. The basic structure stored to disk is made up of command byte/child count byte pairs. The command bytes are listed in makebf.h. The mission editor is able to communicate with the main game via the scratchpad. It can send "MED: directory file" or "MED: directory". If the main program doesn't delete this message with a few seconds then it is launched again. The mission editor also supports a command-line interface to decompile one battlefield back into usersrc\custom.bfi: meditor directory\file.bf The Family statement in the custom mission is generated by the following fprint statement: fprintf(f, "%i %i " "%hi %hi " "%c %c " "%hi %hi %hi " "%hi %hi %hi " "%hi %hi " "%hi %hi " "%hi %hi %hi " , missiondate,timeofday, numofsquadronac,numofflightac, PlayerNationality+'@',formationtypeindex+'@', weathernum,windspeed,winddirection, era,region,PlayerAcType, PlayerHomeBase,EnemyHomeBase, MainObjective,SubObjective, WP1,WP2,WP3 ); The second description is the news. The main game considers all directories beginning with "M" to be mission groups, plus "default"; all that begin with "C" are campaigns. The dictionary system in MAKEBFW is unable to cope with filenames that start with digits. In a campaign directory, the dir.dir offers access to the campaign initialisation data as index 251, and the pilots as 252. The entries 1..250 form the sequential missions for the campaign. The campaign structure is in mecamp.h The pilots are each: struct PilotType { TextRef name; /word filled at runtime SkillType skill; /byte pilotstatus status; /byte CharacterType character; /byte MoraleType morale; /byte NextFollAct nextfollact; /byte SWord dayssincerest; SWord kills; SWord daystorecover; struct ImageMaps { UByte topwing; UByte tail; UByte btmwing; UByte fuselage; UByte other; UByte emblem; } imagemaps; }; followed by a 50 byte name field. The mission editor is unable to modify the main world. This is specified by the region as a rowan file number. The upper byte specifies the directory number, and the lower byte is the index in dir.dir. Additional base worlds could be saved in the campaign directory by counting backwards from 250. Unfortunately, a directory may only contain 254 files maximum including dir.dir at position 0. UniqueIDs --------- The location of objects is driven by uniqueids. In makebf, an object can be placed with only a umiqueid band. At runtime such objects will be allocated uniqueids within that band sequentially. The bands are listed in uniqueid.h However, if the object is referenced either within the MAKEBFW system, or by the main program, then it will be assigned a permanent uniqueID within it's band by MAKEBFW. MAKEBFW assigns these downward from the top of the band, but as far as the runtime game is concerned they are cast in stone in uidvals.g, which MAKEBFW outputs. The same object can exist in different base era files by applying the same SetUID instruction in both items. Needless to say, every object should get a unique uniqeid. If two objects with the same UID are loaded, or a band overflows the game will emit an error message and stop. This is particularly a problem in events, where complex waypoints positions often refer to each other forcing MAKEBFW generate a permanent uid. This event then cannot be loaded twice in the same mission. One important feature of MAKEBFW is that it can forward-reference within the source. However, it sometimes needs help to decide what type a symbol is. If it guesses wrong then it will generate an error on reaching the actual definition. MAKEBFW only generates one error message and then stops. Press a key to exit. /Jim